﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.CodeDom.Compiler;
using NBusiness.CodeDom.Compiler;
using NBusiness.Templates;
using System.CodeDom;
using System.IO;
using NBusiness.CodeDom;

namespace NBusiness.VisualStudio.Generators
{
    /// <summary>
    /// This is the generator class. 
    /// When setting the 'Custom Tool' property of a C#, VB, or J# project item to "XmlClassGenerator", 
    /// the GenerateCode function will get called and will return the contents of the generated file 
    /// to the project system
    /// </summary>
    [ComVisible(true)]
    [Serializable]
    public abstract class TemplateGeneratorBase : BaseCodeGeneratorWithSite
    {
        Type _engineType;
        Type _templateAttribute;
        List<string> _imports = new List<string>();

        public TemplateGeneratorBase(Type engineType, Type templateAttribute, params string[] imports)
        {
            _engineType = engineType;
            _templateAttribute = templateAttribute;
            _imports.AddRange(imports);
        }

        /// <summary>
        /// Function that builds the contents of the generated file based on the contents of the input file
        /// </summary>
        /// <param name="inputFileContent">Content of the input file</param>
        /// <returns>Generated file as a byte array</returns>
        protected override byte[] GenerateCode(string inputFileContent)
        {
            CodeDomProvider provider = GetCodeProvider();
            try
            {
                string template = this.GetProjectItem().Document.ReadAllText();
                if (!string.IsNullOrEmpty(template))
                {
                    template = Convert.ToBase64String(Encoding.UTF8.GetBytes(template));

                    CodeCompileUnit unit = new CodeCompileUnit();
                    CodeNamespace ns = unit.Namespace(this.FileNameSpace);
                    ns.Import(_imports.ToArray());

                    CodeTypeDeclaration type = ns.Declare(Path.GetFileNameWithoutExtension(this.GetProjectItem().Name));
                    type.Attributes = MemberAttributes.Public;
                    type.Implements(typeof(TemplateBase));
                    type.Define(_templateAttribute);

                    // Example output:
                    //protected override string Compile(Entity entity, EntityCompileParameters parameters)
                    //{
                    //    ICodeGeneratorEngine engine = CodeGeneratorEngine.GetEngine();
                    //
                    //    string template = Encoding.UTF8.GetString(Convert.FromBase64String("base64 string..."));
                    //    string generated = engine.Generate(entity, template, parameters);
                    //
                    //    return generated;
                    //}
                    CodeMemberMethod method = type.Method("Compile");
                    method.Attributes = MemberAttributes.Override | MemberAttributes.Family;
                    method.ReturnType = typeof(string).Reference();
                    CodeParameterDeclarationExpression entity = method.Parameter(typeof(Entity), "entity");
                    CodeParameterDeclarationExpression parameters = method.Parameter(typeof(EntityCompileParameters), "parameters");

                    CodeObjectCreateExpression newEngine = new CodeObjectCreateExpression(_engineType);
                    
                    CodeMethodReferenceExpression getString = new CodeMethodReferenceExpression(
                        new CodePropertyReferenceExpression(
                            typeof(Encoding).Expression(),
                            "UTF8"),
                            "GetString");
                    
                    CodeMethodReferenceExpression fromBase64String = new CodeMethodReferenceExpression(
                        typeof(Convert).Expression(),
                        "FromBase64String");

                    CodeVariableDeclarationStatement engineDeclaration = method.Declare(
                        typeof(ICodeGeneratorEngine),
                        "engine",
                        newEngine);
                    
                    CodeVariableDeclarationStatement templateDeclaration = method.Declare(
                        typeof(string),
                        "template",
                        getString.Invoke(
                        fromBase64String.Invoke(
                        new CodePrimitiveExpression(template))));

                    CodeMethodReferenceExpression generate = new CodeMethodReferenceExpression(
                        engineDeclaration.Reference(),
                        "Generate");

                    CodeVariableDeclarationStatement generatedDeclaration = method.Declare(
                        typeof(string),
                        "generated",
                        generate.Invoke(
                            entity.Reference(),
                            templateDeclaration.Reference(),
                            parameters.Reference()));

                    CodeMethodReturnStatement ret = method.Return(
                        generatedDeclaration.Reference());

                    string code = provider.ToCode(unit);
                    return Encoding.UTF8.GetBytes(code);
                }
            }
            catch (Exception e)
            {
                this.GeneratorError(4, e.ToString(), 1, 1);
            }
            finally
            {
                if (this.CodeGeneratorProgress != null)
                {
                    //Report that we are done
                    this.CodeGeneratorProgress.Progress(100, 100);
                }
            }

            //Returning null signifies that generation has failed
            return null;
        }
    }
}
